package com.enation.app.javashop.framework.redis.transactional;


import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;


import com.enation.app.javashop.framework.util.StringUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.CodeSignature;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;


/***
 * Redis事务切面控制
 * @author kingapex
 * @version 2.0:使用 Redisson
 * @since 6.4.1
 * 2017年6月9日上午10:13:27
 */
@Aspect
@Component
public class RedisTransactionalAspect {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private RedissonClient redissonClient;

    private ExpressionParser parser = new SpelExpressionParser();

    private LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();



    /**
     * 对事务注解进行切面
     *
     * @param pjd           切点
     * @param transactional 事务注解
     * @return 原方法返回值
     * @throws Throwable 可能存在的异常
     */
    @Around(value = "@annotation(transactional)")
    public Object aroundMethod(ProceedingJoinPoint pjd, RedisTransactional transactional) throws Throwable {
        Method method = ((MethodSignature) pjd.getSignature()).getMethod();
        RedisTransactional lockAction = method.getAnnotation(RedisTransactional.class);
        String funcationKey =  lockAction.lockName();
        String key = lockAction.value();
        Object[] args = pjd.getArgs();
        String lockName  = funcationKey + parse(key, method, args);

        if (StringUtil.isEmpty(lockName)) {
            //生成 lock key name
            CodeSignature signature = (CodeSignature) pjd.getSignature();
            lockName = signature.toLongString();
        }

        //获取锁
        logger.debug("【取锁线程】："+Thread.currentThread().getName());
        RLock lock = redissonClient.getLock(lockName);
        try {
            //上锁

            //获取锁的最长时间
            int acquireTimeout = transactional.acquireTimeout();

            //锁的超时间
            int lockTime = transactional.lockTimeout();

            //如果没指定超时时间则直接上锁
            if (lockTime == 0 && acquireTimeout == 0) {
                lock.lock();

            }

            //如果指定了超时间则尝试上锁（获取锁最长等待时间---acquireTimeout，上锁最长时间---lockTime）
            if (acquireTimeout != 0 && lockTime != 0) {
                boolean lockResult = lock.tryLock(acquireTimeout, lockTime, TimeUnit.SECONDS);

                // 失败之后sleepMills,重新尝试获取锁
                if (!lockResult) {
                    throw new RuntimeException(lockName + " 获取锁失败");
                }


            }

            //如果指定了 锁的超时间，但没有指定获取锁的时间
            if (acquireTimeout == 0 && lockTime != 0) {
                lock.lock(lockTime, TimeUnit.SECONDS);

            }


            //执行切面方法
            Object result = pjd.proceed();
            return result;

        } catch (Throwable e) {
            if (logger.isErrorEnabled()) {
                this.logger.error("redis事务失败", e);
            }
            throw e;
        } finally {
            //解锁
            lock.unlock();
        }
    }


    /**
     * @description 解析spring EL表达式
     * @author fuwei.deng
     * @date 2018年1月9日 上午10:41:01
     * @version 1.0.0
     * @param key 表达式
     * @param method 方法
     * @param args 方法参数
     * @return
     */
    private String parse(String key, Method method, Object[] args) {
        String[] params = discoverer.getParameterNames(method);
        EvaluationContext context = new StandardEvaluationContext();

        for (int i = 0; i < params.length; i ++) {
            context.setVariable(params[i], args[i]);
        }

        return parser.parseExpression(key).getValue(context, String.class);
    }

}
